import { createLocalCert, getIps, HttpsOptions } from "albert.gao.mkcert";
import { exec } from "child_process";
import killPort from "cross-port-killer";
import Express from "express";
import { type as osType } from "os";
import WebSocket, { Server as WSServer } from "ws";
export { getIps, createLocalCert };
import {
  createServer as createHttpXServer,
  Server as SuperServer,
} from "@httptoolkit/httpolyglot";
import http from "http";

/**
 * 传入的serverSetup所用的函数
 * serverInstance
 */
export interface ServerInstance {
  /**
   * Express的app
   */
  app: Express.Express;
  // httpServer?: HttpServerInterface;
  // httpsServer?: HttpsServerInterface;
  server?: SuperServer | http.Server;
  wss: WSServer;
  broadcast?: (data: any) => any;
  httpsOptions?: HttpsOptions | true;
}
export type ServerSetupFunc = (options: ServerInstance) => any;
/**
 * expressWssServer的参数
 */
export interface ExpressWssServerOptionsDeprecated {
  /**
   * https的配置，
   * 如果是true，则使用默认的local https配置
   * @default false
   */
  httpsOptions?: HttpsOptions | true;
  /**
   * 设置server的函数数组
   */
  creations?: ServerSetupFunc[];
  /**
   * 设置server的函数数组
   */
  setup?: ServerSetupFunc[];
  /**
   * https和http的共用端口
   */
  port?: number;
  /**
   * 是否跨域
   */
  CORS?: boolean;
  /**
   * 当port被占用时是否自动分配一个新的端口
   */
  changePortWhenOccupied?: boolean;
  /**
   * 当port被占用时是否自动kill
   */
  killPortWhenOccupied?: boolean;
  /**
   * 在启动时是否log IP等信息
   */
  logOnStart?: boolean;
}
/**
 * expressWssServer的返回结果
 */
export interface ExpressWssResult
  extends ExpressWssServerOptionsDeprecated,
    ServerInstance {
  /**
   * http和https协议共同使用的port
   */
  proxyPort: number;
}

export interface HandleWsClientFuncArg {
  /**
   * WebSocketServer实例
   */
  wss: WSServer;
  /**
   * 广播实例，广播时，内容会自动JSON.stringify
   */
  broadcast: Function;
  /**
   * 收到的msg，未经过JSON.parse
   */
  msg: string;
  /**
   * 收到的json, 已经过JSON.parse
   */
  json?: any;
  /**
   * 所有的client
   */
  clients: WebSocket.WebSocket[];
  /**
   * 当前的client
   */
  client: WebSocket.WebSocket;
  /**
   * 回复函数、发送的数据会经过JSON.stringify
   */
  reply: (
    /**
     * 要发送的数据
     */
    data: any
  ) => any | void;
}
export interface WsHandler {
  e?:
    | "close"
    | "error"
    | "message"
    | "upgrade"
    | "open"
    | "ping"
    | "pong"
    | "unexpected-response";
  func?: (options: HandleWsClientFuncArg) => any | void;
}

/**
 * 判断https?开头的url是否属于本地ip
 * @param url url
 * @returns 是否属于本地ip
 */
export function isLocalhost(url: string): boolean {
  const ips = getIps();
  for (let ip of ips) {
    ip = ip.replace(/\./g, "\\.");
    const reg = new RegExp(`^(https?:/)?/${ip}`, "i");
    if (reg.test(url)) {
      return true;
    }
  }
  return false;
}

/**
 * 获取已被占用的端口
 * @returns 已被占用的端口
 */
export function getOccupiedPorts(): Promise<number[]> {
  const command = /windows/i.test(osType()) ? "netstat -ano" : "netstat -tunlp";
  return new Promise((resolve) => {
    exec(command, (err, stdout) => {
      err && console.error(err);
      const regs0 = /\s+\d+(\.\d+){3}:(?<port>\d+)\s+/g;
      const regs1 = /\s+(\[::\]|::):(?<port>\d+)\s+/g;
      const arr0 = [
        ...(`${stdout}`.match(regs0) || []),
        ...(`${stdout}`.match(regs1) || []),
      ]
        .map((e) => {
          const groups = [...e.split(":")];
          return groups.pop();
        })
        .map((e) => (e ? +e : 0));
      const result = Array.from(new Set(arr0)).filter((e) => e);
      resolve(result);
    });
  });
}

/**
 * 根据需要的端口号是否被占用按需获取一个新的端口号
 * @param port 需要的端口号
 * @param other 其他不可用的端口号
 * @returns 获取的新的端口号
 */
export async function getUnOccupiedPort(
  port: string | number,
  ...other: string[] | number[]
) {
  port = +port;
  const occupied: number[] = await getOccupiedPorts();
  let _occupied: number[] = [occupied, [...other].map((e) => +e)].flat();
  while (_occupied.includes(port)) {
    port++;
  }
  return port;
}

/**
 * 创建一个同时有express和webSocketServer的服务器
 * @deprecated
 * @param option 服务器参数
 * @returns  服务器实例
 */
export async function expressWssServer(
  option: ExpressWssServerOptionsDeprecated
): Promise<ExpressWssResult> {
  let {
    httpsOptions,
    creations = [],
    setup = [],
    port = 8080,
    CORS = true,
    logOnStart = true,
    changePortWhenOccupied = true,
    killPortWhenOccupied = false,
  } = option;
  let httpsOptionsActual: HttpsOptions;
  if (httpsOptions === true) {
    httpsOptionsActual = await createLocalCert({});
  } else if (httpsOptions) {
    httpsOptionsActual = httpsOptions;
  } else {
    httpsOptionsActual = {
      key: Buffer.from([]),
      cert: Buffer.from([]),
    };
  }
  const _createServer = (app = Express()) =>
    httpsOptions
      ? createHttpXServer(httpsOptionsActual, app)
      : http.createServer(app);
  const serverInstance = { app: Express() } as ServerInstance;
  const serverInitialization: Object = {
    addExpress: function ({ app = Express() }: ServerInstance) {
      app.use(Express.json({ limit: "100mb" }));
      app.use(Express.urlencoded({ extended: true, limit: "100mb" }));
      Object.assign(serverInstance, { app });
    },
    addHttpServer: function ({
      app = Express(),
      server = _createServer(app),
    }: ServerInstance) {
      Object.assign(serverInstance, { app, server });
    },
    addWss: function ({
      app = Express(),
      server = _createServer(app),
      // @ts-ignore
      wss = new WSServer({ server }),
      broadcast = (data: any) => {
        wss.clients.forEach(function each(client) {
          if (client.readyState === WebSocket.OPEN) {
            client.send(JSON.stringify(data));
          }
        });
      },
    }: ServerInstance) {
      Object.assign(serverInstance, {
        app,
        server,
        wss,
        broadcast,
      });
    },
  };
  for (const func of Object.values(serverInitialization)) {
    func(serverInstance);
  }
  for (const func of [...creations, ...setup]) {
    const _result = func(serverInstance);
    await _result;
  }
  if (CORS) {
    // 最后设置设置跨域
    serverInstance.app.use((req, res, next) => {
      // Access-Control-Allow-Credentials
      res.set({
        "Access-Control-Allow-Credentials": true,
        "Access-Control-Max-Age": 1728000,
        "Access-Control-Allow-Origin": req.headers.origin || "*",
        "Access-Control-Allow-Headers": "X-Requested-With,Content-Type",
        "Access-Control-Allow-Methods": "PUT,POST,GET,DELETE,OPTIONS",
        "Content-Type": "application/json; charset=utf-8",
      });
      req.method === "OPTIONS" ? res.status(204).end() : next();
    });
  }
  const { app, server = _createServer(app) } = serverInstance;
  if (changePortWhenOccupied) {
    port = await getUnOccupiedPort(port);
    server.listen(port);
  } else if (killPortWhenOccupied) {
    await killPort(port);
    server.listen(port);
  } else {
    server.listen(port);
  }
  for (const ip of getIps()) {
    logOnStart && console.log("server listening at: " + `http://${ip}:${port}`);
  }
  return {
    ...option,
    ...serverInstance,
    proxyPort: port,
    port,
  };
}

/**
 * 获取WebSocketServer的所有用户
 * @param wssArr WebSocketServer列表
 * @param onlineOnly 是否只筛选在线client
 * @returns 返回clients数组
 */
export function getWssClients(
  wssArr: WSServer[],
  onlineOnly: boolean = true
): WebSocket.WebSocket[] {
  let arr = [];
  for (const { clients } of wssArr) {
    arr.push(...Array.from(clients));
  }
  if (onlineOnly) {
    return arr.filter((e) => e.readyState === WebSocket.OPEN);
  } else {
    return arr;
  }
}

/**
 * 批量处理webSocketServer的函数
 * @param wssArr WebSocketServer数组
 * @param broadcast 广播函数
 * @param handlers 处理函数
 */
export function handleWsClient(
  wssArr: WSServer[] = [],
  broadcast: Function = () => 0,
  handlers: WsHandler[] = []
): void {
  for (const wss of wssArr) {
    wss.on("connection", function connection(ws) {
      for (const { e = "message", func = () => 0 } of handlers) {
        ws.on(e, (msg: any) => {
          let reply = (data: any) => ws.send(JSON.stringify(data));
          try {
            const json = JSON.parse(msg);
            func({
              wss,
              broadcast,
              json,
              msg,
              client: ws,
              clients: getWssClients(wssArr),
              reply,
            });
          } catch (error) {
            func({
              wss,
              broadcast,
              msg,
              client: ws,
              clients: getWssClients(wssArr),
              reply,
            });
          }
        });
      }
    });
  }
}
